// Copyright (c) 2024 刻BITTER
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.


#pragma once

/**
 * @brief   适用于HK32F030M 风格的GPIO 操作
 */


#include "common_cm_pin.hpp"
#include "ioxx_common.hpp"
#include "ioxx_include_device.hpp"  // 引入CMSIS 器件头文件，可能需要用户定义的配置文件


// 时钟
#define _IOXX_GPIO_ENABLE_CLOCK _SUPER_CAT3(RCC_, _IOXX_GPIO_BUS, PeriphClockCmd)
// #define _IOXX_GPIO_DISABLE_CLOCK _SUPER_CAT3(LL_, _IOXX_GPIO_BUS, _DisableClock)

#ifdef _IOXX_GPIO_BUS_2
    #define _IOXX__GPIO_ENABLE_CLOCK_2 _SUPER_CAT3(RCC_, _IOXX_GPIO_BUS_2, PeriphClockCmd)
#endif

namespace ioxx {

    /**
     * @brief 打开所有GPIO 端口的时钟，AFIO 的时钟也会被打开
     *
     * IOXX 的目标是尽量提供接近Arduino 的开发效率，所以不直接提供太细粒度的控制，
     * 如果在低功耗场合需要精细的考虑外设的时钟，那就使用更底层的工具
     */
    inline void enable_gpio() {
        _IOXX_GPIO_ENABLE_CLOCK(_IOXX_GPIO_ALL_BUS_MASK, ENABLE);

#ifdef _IOXX_GPIO_BUS_2

        _IOXX__GPIO_ENABLE_CLOCK_2(_IOXX_GPIO_ALL_BUS_MASK_2, ENABLE);

#endif
    }

    inline void disable_gpio() {
        _IOXX_GPIO_ENABLE_CLOCK(_IOXX_GPIO_ALL_BUS_MASK, DISABLE);

#ifdef _IOXX_GPIO_BUS_2

        _IOXX__GPIO_ENABLE_CLOCK_2(_IOXX_GPIO_ALL_BUS_MASK_2, DISABLE);

#endif
    }

    // inline void enable_gpio(GPIO_TypeDef *port) {

    // }

    // inline void disable_gpio(GPIO_TypeDef *port) {
    // }
}  // namespace ioxx


// 模式

namespace ioxx {

    enum class mode {
        in = GPIO_Mode_IN,
        out = GPIO_Mode_OUT,
        alt = GPIO_Mode_AF,
        analog = GPIO_Mode_AN,
    };


    enum class drive {
        push_pull = GPIO_OType_PP,
        open_drain = GPIO_OType_OD,
    };


    enum class pull {
        none = GPIO_PuPd_NOPULL,
        up = GPIO_PuPd_UP,
        down = GPIO_PuPd_DOWN,
    };


    enum class speed {
        low = GPIO_Speed_Level_1,
        medium = GPIO_Speed_Level_2,

        high = medium,
        very_high = high,

        lowest = low,
        highest = very_high,
        normal = medium,
    };


    // template <typename PinType = _DefaultPinType>
    class PinInit {
       public:
        using PinType = Pin;

       private:
        // 引脚默认配置：输入模式、输出频率最高、推挽输出、无上下拉
        // 上下拉电阻在输出模式下也会生效，会产生不必要的电流，所以默认关闭上下拉
        GPIO_InitTypeDef _param{
            .GPIO_Pin = 0,
            .GPIO_Mode = _IOXX_ENUM_TO_ENUM(mode::in, GPIOMode_TypeDef),
            .GPIO_Speed = _IOXX_ENUM_TO_ENUM(speed::normal, GPIOSpeed_TypeDef),
            .GPIO_OType = _IOXX_ENUM_TO_ENUM(drive::push_pull, GPIOOType_TypeDef),
            .GPIO_PuPd = _IOXX_ENUM_TO_ENUM(pull::none, GPIOPuPd_TypeDef),

#ifdef _IOXX_GPIO_SCHMIT_AVAILABLE
            .GPIO_Schmit = GPIO_Schmit_Enable,  // 没有必要手动开关施密特
#endif

#ifdef _IOXX_GPIO_AF_SELECTION_AVAILABLE
            .Alternate = 0,
#endif
        };

       public:
        PinInit &mode(ioxx::mode m) {
            _param.GPIO_Mode = _IOXX_ENUM_TO_ENUM(m, GPIOMode_TypeDef);
            return *this;
        }

        PinInit &speed(ioxx::speed s) {
            _param.GPIO_Speed = _IOXX_ENUM_TO_ENUM(s, GPIOSpeed_TypeDef);
            return *this;
        }

        PinInit &drive(ioxx::drive d) {
            _param.GPIO_OType = _IOXX_ENUM_TO_ENUM(d, GPIOOType_TypeDef);
            return *this;
        }

        PinInit &pull(ioxx::pull p) {
            _param.GPIO_PuPd = _IOXX_ENUM_TO_ENUM(p, GPIOPuPd_TypeDef);
            return *this;
        }

#ifdef _IOXX_GPIO_AF_SELECTION_AVAILABLE

        PinInit &af(uint32_t af_select) {
            _param.Alternate = af_select;
            return *this;
        }

#endif

        PinInit &init(const PinType &pin) {
            _param.GPIO_Pin = pin.pin_mask();
            GPIO_Init(pin.port(), &_param);
            return *this;
        }
    };


    inline void set_pin_mode(const Pin &p, mode m) {
        GPIO_TypeDef *port = p.port();

        port->MODER &= ~(GPIO_MODER_MODER0 << (p.pin_num() * 2));
        port->MODER |= (static_cast<uint32_t>(m) << (p.pin_num() * 2));
    }


    inline void set_pin_drive(const Pin &p, drive d) {
        GPIO_TypeDef *port = p.port();

        port->OTYPER &= ~((GPIO_OTYPER_OT_0) << ((uint16_t)p.pin_num()));
        port->OTYPER |= (uint16_t)(((uint16_t)d) << ((uint16_t)p.pin_num()));
    }


    inline void set_pin_speed(const Pin &p, speed d) {
        GPIO_TypeDef *port = p.port();

        port->OSPEEDR &= ~(GPIO_OSPEEDR_OSPEEDR0 << (p.pin_num() * 2));
        port->OSPEEDR |= (static_cast<uint32_t>(d) << (p.pin_num() * 2));
    }


    inline void set_pin_pull(const Pin &p, pull pl) {
        GPIO_TypeDef *port = p.port();

        port->PUPDR &= ~(GPIO_PUPDR_PUPDR0 << ((uint16_t)p.pin_num() * 2));
        port->PUPDR |= (((uint32_t)pl) << (p.pin_num() * 2));
    }

}  // namespace ioxx